home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Chat & Communication / Digsby build 37 / digsby_setup.exe / lib / difflib.pyo (.txt) < prev    next >
Python Compiled Bytecode  |  2008-10-13  |  26KB  |  943 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. __all__ = [
  5.     'get_close_matches',
  6.     'ndiff',
  7.     'restore',
  8.     'SequenceMatcher',
  9.     'Differ',
  10.     'IS_CHARACTER_JUNK',
  11.     'IS_LINE_JUNK',
  12.     'context_diff',
  13.     'unified_diff',
  14.     'HtmlDiff']
  15. import heapq
  16.  
  17. def _calculate_ratio(matches, length):
  18.     if length:
  19.         return 2 * matches / length
  20.     
  21.     return 1
  22.  
  23.  
  24. class SequenceMatcher:
  25.     
  26.     def __init__(self, isjunk = None, a = '', b = ''):
  27.         self.isjunk = isjunk
  28.         self.a = None
  29.         self.b = None
  30.         self.set_seqs(a, b)
  31.  
  32.     
  33.     def set_seqs(self, a, b):
  34.         self.set_seq1(a)
  35.         self.set_seq2(b)
  36.  
  37.     
  38.     def set_seq1(self, a):
  39.         if a is self.a:
  40.             return None
  41.         
  42.         self.a = a
  43.         self.matching_blocks = None
  44.         self.opcodes = None
  45.  
  46.     
  47.     def set_seq2(self, b):
  48.         if b is self.b:
  49.             return None
  50.         
  51.         self.b = b
  52.         self.matching_blocks = None
  53.         self.opcodes = None
  54.         self.fullbcount = None
  55.         self._SequenceMatcher__chain_b()
  56.  
  57.     
  58.     def __chain_b(self):
  59.         b = self.b
  60.         n = len(b)
  61.         self.b2j = b2j = { }
  62.         populardict = { }
  63.         for i, elt in enumerate(b):
  64.             if elt in b2j:
  65.                 indices = b2j[elt]
  66.                 if n >= 200 and len(indices) * 100 > n:
  67.                     populardict[elt] = 1
  68.                     del indices[:]
  69.                 else:
  70.                     indices.append(i)
  71.             len(indices) * 100 > n
  72.             b2j[elt] = [
  73.                 i]
  74.         
  75.         for elt in populardict:
  76.             del b2j[elt]
  77.         
  78.         isjunk = self.isjunk
  79.         junkdict = { }
  80.         if isjunk:
  81.             for d in (populardict, b2j):
  82.                 for elt in d.keys():
  83.                     if isjunk(elt):
  84.                         junkdict[elt] = 1
  85.                         del d[elt]
  86.                         continue
  87.                 
  88.             
  89.         
  90.         self.isbjunk = junkdict.has_key
  91.         self.isbpopular = populardict.has_key
  92.  
  93.     
  94.     def find_longest_match(self, alo, ahi, blo, bhi):
  95.         (a, b, b2j, isbjunk) = (self.a, self.b, self.b2j, self.isbjunk)
  96.         besti = alo
  97.         bestj = blo
  98.         bestsize = 0
  99.         j2len = { }
  100.         nothing = []
  101.         for i in xrange(alo, ahi):
  102.             j2lenget = j2len.get
  103.             newj2len = { }
  104.             for j in b2j.get(a[i], nothing):
  105.                 if j < blo:
  106.                     continue
  107.                 
  108.                 if j >= bhi:
  109.                     break
  110.                 
  111.                 k = newj2len[j] = j2lenget(j - 1, 0) + 1
  112.                 if k > bestsize:
  113.                     besti = (i - k) + 1
  114.                     bestj = (j - k) + 1
  115.                     bestsize = k
  116.                     continue
  117.             
  118.             j2len = newj2len
  119.         
  120.         while besti > alo and bestj > blo and not isbjunk(b[bestj - 1]) and a[besti - 1] == b[bestj - 1]:
  121.             besti = besti - 1
  122.             bestj = bestj - 1
  123.             bestsize = bestsize + 1
  124.         while besti + bestsize < ahi and bestj + bestsize < bhi and not isbjunk(b[bestj + bestsize]) and a[besti + bestsize] == b[bestj + bestsize]:
  125.             bestsize += 1
  126.         while besti > alo and bestj > blo and isbjunk(b[bestj - 1]) and a[besti - 1] == b[bestj - 1]:
  127.             besti = besti - 1
  128.             bestj = bestj - 1
  129.             bestsize = bestsize + 1
  130.         while besti + bestsize < ahi and bestj + bestsize < bhi and isbjunk(b[bestj + bestsize]) and a[besti + bestsize] == b[bestj + bestsize]:
  131.             bestsize = bestsize + 1
  132.         return (besti, bestj, bestsize)
  133.  
  134.     
  135.     def get_matching_blocks(self):
  136.         if self.matching_blocks is not None:
  137.             return self.matching_blocks
  138.         
  139.         la = len(self.a)
  140.         lb = len(self.b)
  141.         queue = [
  142.             (0, la, 0, lb)]
  143.         matching_blocks = []
  144.         while queue:
  145.             (alo, ahi, blo, bhi) = queue.pop()
  146.             (i, j, k) = x = self.find_longest_match(alo, ahi, blo, bhi)
  147.             if k:
  148.                 matching_blocks.append(x)
  149.                 if alo < i and blo < j:
  150.                     queue.append((alo, i, blo, j))
  151.                 
  152.                 if i + k < ahi and j + k < bhi:
  153.                     queue.append((i + k, ahi, j + k, bhi))
  154.                 
  155.             j + k < bhi
  156.         matching_blocks.sort()
  157.         i1 = j1 = k1 = 0
  158.         non_adjacent = []
  159.         for i2, j2, k2 in matching_blocks:
  160.             if i1 + k1 == i2 and j1 + k1 == j2:
  161.                 k1 += k2
  162.                 continue
  163.             if k1:
  164.                 non_adjacent.append((i1, j1, k1))
  165.             
  166.             i1 = i2
  167.             j1 = j2
  168.             k1 = k2
  169.         
  170.         if k1:
  171.             non_adjacent.append((i1, j1, k1))
  172.         
  173.         non_adjacent.append((la, lb, 0))
  174.         self.matching_blocks = non_adjacent
  175.         return self.matching_blocks
  176.  
  177.     
  178.     def get_opcodes(self):
  179.         if self.opcodes is not None:
  180.             return self.opcodes
  181.         
  182.         i = j = 0
  183.         self.opcodes = answer = []
  184.         for ai, bj, size in self.get_matching_blocks():
  185.             tag = ''
  186.             if i < ai and j < bj:
  187.                 tag = 'replace'
  188.             elif i < ai:
  189.                 tag = 'delete'
  190.             elif j < bj:
  191.                 tag = 'insert'
  192.             
  193.             if tag:
  194.                 answer.append((tag, i, ai, j, bj))
  195.             
  196.             i = ai + size
  197.             j = bj + size
  198.             if size:
  199.                 answer.append(('equal', ai, i, bj, j))
  200.                 continue
  201.         
  202.         return answer
  203.  
  204.     
  205.     def get_grouped_opcodes(self, n = 3):
  206.         codes = self.get_opcodes()
  207.         if not codes:
  208.             codes = [
  209.                 ('equal', 0, 1, 0, 1)]
  210.         
  211.         if codes[0][0] == 'equal':
  212.             (tag, i1, i2, j1, j2) = codes[0]
  213.             codes[0] = (tag, max(i1, i2 - n), i2, max(j1, j2 - n), j2)
  214.         
  215.         if codes[-1][0] == 'equal':
  216.             (tag, i1, i2, j1, j2) = codes[-1]
  217.             codes[-1] = (tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n))
  218.         
  219.         nn = n + n
  220.         group = []
  221.         for tag, i1, i2, j1, j2 in codes:
  222.             if tag == 'equal' and i2 - i1 > nn:
  223.                 group.append((tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n)))
  224.                 yield group
  225.                 group = []
  226.                 i1 = max(i1, i2 - n)
  227.                 j1 = max(j1, j2 - n)
  228.             
  229.             group.append((tag, i1, i2, j1, j2))
  230.         
  231.         if group:
  232.             if len(group) == 1:
  233.                 pass
  234.             if not (group[0][0] == 'equal'):
  235.                 yield group
  236.             
  237.  
  238.     
  239.     def ratio(self):
  240.         matches = reduce((lambda sum, triple: sum + triple[-1]), self.get_matching_blocks(), 0)
  241.         return _calculate_ratio(matches, len(self.a) + len(self.b))
  242.  
  243.     
  244.     def quick_ratio(self):
  245.         if self.fullbcount is None:
  246.             self.fullbcount = fullbcount = { }
  247.             for elt in self.b:
  248.                 fullbcount[elt] = fullbcount.get(elt, 0) + 1
  249.             
  250.         
  251.         fullbcount = self.fullbcount
  252.         avail = { }
  253.         availhas = avail.has_key
  254.         matches = 0
  255.         for elt in self.a:
  256.             if availhas(elt):
  257.                 numb = avail[elt]
  258.             else:
  259.                 numb = fullbcount.get(elt, 0)
  260.             avail[elt] = numb - 1
  261.             if numb > 0:
  262.                 matches = matches + 1
  263.                 continue
  264.         
  265.         return _calculate_ratio(matches, len(self.a) + len(self.b))
  266.  
  267.     
  268.     def real_quick_ratio(self):
  269.         la = len(self.a)
  270.         lb = len(self.b)
  271.         return _calculate_ratio(min(la, lb), la + lb)
  272.  
  273.  
  274.  
  275. def get_close_matches(word, possibilities, n = 3, cutoff = 0.6):
  276.     if not n > 0:
  277.         raise ValueError('n must be > 0: %r' % (n,))
  278.     
  279.     if cutoff <= cutoff:
  280.         pass
  281.     elif not cutoff <= 1:
  282.         raise ValueError('cutoff must be in [0.0, 1.0]: %r' % (cutoff,))
  283.     
  284.     result = []
  285.     s = SequenceMatcher()
  286.     s.set_seq2(word)
  287.     for x in possibilities:
  288.         s.set_seq1(x)
  289.         if s.real_quick_ratio() >= cutoff and s.quick_ratio() >= cutoff and s.ratio() >= cutoff:
  290.             result.append((s.ratio(), x))
  291.             continue
  292.         0
  293.     
  294.     result = heapq.nlargest(n, result)
  295.     return [ x for score, x in result ]
  296.  
  297.  
  298. def _count_leading(line, ch):
  299.     i = 0
  300.     n = len(line)
  301.     while i < n and line[i] == ch:
  302.         i += 1
  303.     return i
  304.  
  305.  
  306. class Differ:
  307.     
  308.     def __init__(self, linejunk = None, charjunk = None):
  309.         self.linejunk = linejunk
  310.         self.charjunk = charjunk
  311.  
  312.     
  313.     def compare(self, a, b):
  314.         cruncher = SequenceMatcher(self.linejunk, a, b)
  315.         for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
  316.             if tag == 'replace':
  317.                 g = self._fancy_replace(a, alo, ahi, b, blo, bhi)
  318.             elif tag == 'delete':
  319.                 g = self._dump('-', a, alo, ahi)
  320.             elif tag == 'insert':
  321.                 g = self._dump('+', b, blo, bhi)
  322.             elif tag == 'equal':
  323.                 g = self._dump(' ', a, alo, ahi)
  324.             else:
  325.                 raise ValueError, 'unknown tag %r' % (tag,)
  326.             for line in g:
  327.                 yield line
  328.             
  329.         
  330.  
  331.     
  332.     def _dump(self, tag, x, lo, hi):
  333.         for i in xrange(lo, hi):
  334.             yield '%s %s' % (tag, x[i])
  335.         
  336.  
  337.     
  338.     def _plain_replace(self, a, alo, ahi, b, blo, bhi):
  339.         if bhi - blo < ahi - alo:
  340.             first = self._dump('+', b, blo, bhi)
  341.             second = self._dump('-', a, alo, ahi)
  342.         else:
  343.             first = self._dump('-', a, alo, ahi)
  344.             second = self._dump('+', b, blo, bhi)
  345.         for g in (first, second):
  346.             for line in g:
  347.                 yield line
  348.             
  349.         
  350.  
  351.     
  352.     def _fancy_replace(self, a, alo, ahi, b, blo, bhi):
  353.         (best_ratio, cutoff) = (0.74, 0.75)
  354.         cruncher = SequenceMatcher(self.charjunk)
  355.         (eqi, eqj) = (None, None)
  356.         for j in xrange(blo, bhi):
  357.             bj = b[j]
  358.             cruncher.set_seq2(bj)
  359.             for i in xrange(alo, ahi):
  360.                 ai = a[i]
  361.                 if ai == bj:
  362.                     if eqi is None:
  363.                         eqi = i
  364.                         eqj = j
  365.                         continue
  366.                     continue
  367.                 
  368.                 cruncher.set_seq1(ai)
  369.                 if cruncher.real_quick_ratio() > best_ratio and cruncher.quick_ratio() > best_ratio and cruncher.ratio() > best_ratio:
  370.                     best_ratio = cruncher.ratio()
  371.                     best_i = i
  372.                     best_j = j
  373.                     continue
  374.             
  375.         
  376.         if best_ratio < cutoff:
  377.             if eqi is None:
  378.                 for line in self._plain_replace(a, alo, ahi, b, blo, bhi):
  379.                     yield line
  380.                 
  381.                 return None
  382.             
  383.             best_i = eqi
  384.             best_j = eqj
  385.             best_ratio = 1
  386.         else:
  387.             eqi = None
  388.         for line in self._fancy_helper(a, alo, best_i, b, blo, best_j):
  389.             yield line
  390.         
  391.         aelt = a[best_i]
  392.         belt = b[best_j]
  393.         if eqi is None:
  394.             atags = btags = ''
  395.             cruncher.set_seqs(aelt, belt)
  396.             for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
  397.                 la = ai2 - ai1
  398.                 lb = bj2 - bj1
  399.                 if tag == 'replace':
  400.                     atags += '^' * la
  401.                     btags += '^' * lb
  402.                     continue
  403.                 if tag == 'delete':
  404.                     atags += '-' * la
  405.                     continue
  406.                 if tag == 'insert':
  407.                     btags += '+' * lb
  408.                     continue
  409.                 if tag == 'equal':
  410.                     atags += ' ' * la
  411.                     btags += ' ' * lb
  412.                     continue
  413.                 raise ValueError, 'unknown tag %r' % (tag,)
  414.             
  415.             for line in self._qformat(aelt, belt, atags, btags):
  416.                 yield line
  417.             
  418.         else:
  419.             yield '  ' + aelt
  420.         for line in self._fancy_helper(a, best_i + 1, ahi, b, best_j + 1, bhi):
  421.             yield line
  422.         
  423.  
  424.     
  425.     def _fancy_helper(self, a, alo, ahi, b, blo, bhi):
  426.         g = []
  427.         if alo < ahi:
  428.             if blo < bhi:
  429.                 g = self._fancy_replace(a, alo, ahi, b, blo, bhi)
  430.             else:
  431.                 g = self._dump('-', a, alo, ahi)
  432.         elif blo < bhi:
  433.             g = self._dump('+', b, blo, bhi)
  434.         
  435.         for line in g:
  436.             yield line
  437.         
  438.  
  439.     
  440.     def _qformat(self, aline, bline, atags, btags):
  441.         common = min(_count_leading(aline, '\t'), _count_leading(bline, '\t'))
  442.         common = min(common, _count_leading(atags[:common], ' '))
  443.         atags = atags[common:].rstrip()
  444.         btags = btags[common:].rstrip()
  445.         yield '- ' + aline
  446.         if atags:
  447.             yield '? %s%s\n' % ('\t' * common, atags)
  448.         
  449.         yield '+ ' + bline
  450.         if btags:
  451.             yield '? %s%s\n' % ('\t' * common, btags)
  452.         
  453.  
  454.  
  455. import re
  456.  
  457. def IS_LINE_JUNK(line, pat = re.compile('\\s*#?\\s*$').match):
  458.     return pat(line) is not None
  459.  
  460.  
  461. def IS_CHARACTER_JUNK(ch, ws = ' \t'):
  462.     return ch in ws
  463.  
  464.  
  465. def unified_diff(a, b, fromfile = '', tofile = '', fromfiledate = '', tofiledate = '', n = 3, lineterm = '\n'):
  466.     started = False
  467.     for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n):
  468.         if not started:
  469.             yield '--- %s %s%s' % (fromfile, fromfiledate, lineterm)
  470.             yield '+++ %s %s%s' % (tofile, tofiledate, lineterm)
  471.             started = True
  472.         
  473.         (i1, i2, j1, j2) = (group[0][1], group[-1][2], group[0][3], group[-1][4])
  474.         yield '@@ -%d,%d +%d,%d @@%s' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
  475.         for tag, i1, i2, j1, j2 in group:
  476.             if tag == 'equal':
  477.                 for line in a[i1:i2]:
  478.                     yield ' ' + line
  479.                 
  480.                 continue
  481.             
  482.             if tag == 'replace' or tag == 'delete':
  483.                 for line in a[i1:i2]:
  484.                     yield '-' + line
  485.                 
  486.             
  487.             if tag == 'replace' or tag == 'insert':
  488.                 for line in b[j1:j2]:
  489.                     yield '+' + line
  490.                 
  491.         
  492.     
  493.  
  494.  
  495. def context_diff(a, b, fromfile = '', tofile = '', fromfiledate = '', tofiledate = '', n = 3, lineterm = '\n'):
  496.     started = False
  497.     prefixmap = {
  498.         'insert': '+ ',
  499.         'delete': '- ',
  500.         'replace': '! ',
  501.         'equal': '  ' }
  502.     for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n):
  503.         if not started:
  504.             yield '*** %s %s%s' % (fromfile, fromfiledate, lineterm)
  505.             yield '--- %s %s%s' % (tofile, tofiledate, lineterm)
  506.             started = True
  507.         
  508.         yield '***************%s' % (lineterm,)
  509.         if group[-1][2] - group[0][1] >= 2:
  510.             yield '*** %d,%d ****%s' % (group[0][1] + 1, group[-1][2], lineterm)
  511.         else:
  512.             yield '*** %d ****%s' % (group[-1][2], lineterm)
  513.         visiblechanges = _[1]
  514.         if visiblechanges:
  515.             for tag, i1, i2, _, _ in group:
  516.                 if tag != 'insert':
  517.                     for line in a[i1:i2]:
  518.                         yield prefixmap[tag] + line
  519.                         []
  520.                     
  521.                 []
  522.             
  523.         
  524.         if group[-1][4] - group[0][3] >= 2:
  525.             yield '--- %d,%d ----%s' % (group[0][3] + 1, group[-1][4], lineterm)
  526.         else:
  527.             yield '--- %d ----%s' % (group[-1][4], lineterm)
  528.         visiblechanges = _[2]
  529.         if visiblechanges:
  530.             for tag, _, _, j1, j2 in group:
  531.                 if tag != 'delete':
  532.                     for line in b[j1:j2]:
  533.                         yield prefixmap[tag] + line
  534.                         []
  535.                     
  536.                 []
  537.             
  538.     
  539.  
  540.  
  541. def ndiff(a, b, linejunk = None, charjunk = IS_CHARACTER_JUNK):
  542.     return Differ(linejunk, charjunk).compare(a, b)
  543.  
  544.  
  545. def _mdiff(fromlines, tolines, context = None, linejunk = None, charjunk = IS_CHARACTER_JUNK):
  546.     import re as re
  547.     change_re = re.compile('(\\++|\\-+|\\^+)')
  548.     diff_lines_iterator = ndiff(fromlines, tolines, linejunk, charjunk)
  549.     
  550.     def _make_line(lines, format_key, side, num_lines = ([
  551.         0,
  552.         0],)):
  553.         num_lines[side] += 1
  554.         if format_key is None:
  555.             return (num_lines[side], lines.pop(0)[2:])
  556.         
  557.         if format_key == '?':
  558.             text = lines.pop(0)
  559.             markers = lines.pop(0)
  560.             sub_info = []
  561.             
  562.             def record_sub_info(match_object, sub_info = sub_info):
  563.                 sub_info.append([
  564.                     match_object.group(1)[0],
  565.                     match_object.span()])
  566.                 return match_object.group(1)
  567.  
  568.             change_re.sub(record_sub_info, markers)
  569.             for begin, end in sub_info[::-1]:
  570.                 text = text[0:begin] + '\x00' + key + text[begin:end] + '\x01' + text[end:]
  571.             
  572.             text = text[2:]
  573.         else:
  574.             text = lines.pop(0)[2:]
  575.             if not text:
  576.                 text = ' '
  577.             
  578.             text = '\x00' + format_key + text + '\x01'
  579.         return (num_lines[side], text)
  580.  
  581.     
  582.     def _line_iterator():
  583.         lines = []
  584.         (num_blanks_pending, num_blanks_to_yield) = (0, 0)
  585.         for line in lines:
  586.             continue
  587.             s = _[1](_[1][line[0]])
  588.             if s.startswith('X'):
  589.                 num_blanks_to_yield = num_blanks_pending
  590.             elif s.startswith('-?+?'):
  591.                 yield (_make_line(lines, '?', 0), _make_line(lines, '?', 1), True)
  592.                 []
  593.                 continue
  594.             elif s.startswith('--++'):
  595.                 num_blanks_pending -= 1
  596.                 yield (_make_line(lines, '-', 0), None, True)
  597.                 continue
  598.             elif s.startswith(('--?+', '--+', '- ')):
  599.                 from_line = _make_line(lines, '-', 0)
  600.                 to_line = None
  601.                 num_blanks_to_yield = num_blanks_pending - 1
  602.                 num_blanks_pending = 0
  603.             elif s.startswith('-+?'):
  604.                 yield (_make_line(lines, None, 0), _make_line(lines, '?', 1), True)
  605.                 continue
  606.             elif s.startswith('-?+'):
  607.                 yield (_make_line(lines, '?', 0), _make_line(lines, None, 1), True)
  608.                 continue
  609.             elif s.startswith('-'):
  610.                 num_blanks_pending -= 1
  611.                 yield (_make_line(lines, '-', 0), None, True)
  612.                 continue
  613.             elif s.startswith('+--'):
  614.                 num_blanks_pending += 1
  615.                 yield (None, _make_line(lines, '+', 1), True)
  616.                 continue
  617.             elif s.startswith(('+ ', '+-')):
  618.                 from_line = None
  619.                 to_line = _make_line(lines, '+', 1)
  620.                 num_blanks_to_yield = num_blanks_pending + 1
  621.                 num_blanks_pending = 0
  622.             elif s.startswith('+'):
  623.                 num_blanks_pending += 1
  624.                 yield (None, _make_line(lines, '+', 1), True)
  625.                 continue
  626.             elif s.startswith(' '):
  627.                 yield (_make_line(lines[:], None, 0), _make_line(lines, None, 1), False)
  628.                 continue
  629.             
  630.             while num_blanks_to_yield < 0:
  631.                 num_blanks_to_yield += 1
  632.                 yield (None, ('', '\n'), True)
  633.             while num_blanks_to_yield > 0:
  634.                 num_blanks_to_yield -= 1
  635.                 yield (('', '\n'), None, True)
  636.             if s.startswith('X'):
  637.                 raise StopIteration
  638.                 continue
  639.             yield (from_line, to_line, True)
  640.  
  641.     
  642.     def _line_pair_iterator():
  643.         line_iterator = _line_iterator()
  644.         fromlines = []
  645.         tolines = []
  646.         while True:
  647.             while len(fromlines) == 0 or len(tolines) == 0:
  648.                 (from_line, to_line, found_diff) = line_iterator.next()
  649.                 if from_line is not None:
  650.                     fromlines.append((from_line, found_diff))
  651.                 
  652.                 if to_line is not None:
  653.                     tolines.append((to_line, found_diff))
  654.                     continue
  655.             (from_line, fromDiff) = fromlines.pop(0)
  656.             (to_line, to_diff) = tolines.pop(0)
  657.             if not fromDiff:
  658.                 pass
  659.             yield (from_line, to_line, to_diff)
  660.  
  661.     line_pair_iterator = _line_pair_iterator()
  662.     if context is None:
  663.         while True:
  664.             yield line_pair_iterator.next()
  665.             ((None, None),)
  666.     else:
  667.         context += 1
  668.         lines_to_write = 0
  669.         while True:
  670.             index = 0
  671.             contextLines = [
  672.                 None] * context
  673.             found_diff = False
  674.             while found_diff is False:
  675.                 (from_line, to_line, found_diff) = line_pair_iterator.next()
  676.                 i = index % context
  677.                 contextLines[i] = (from_line, to_line, found_diff)
  678.                 index += 1
  679.                 continue
  680.                 ((None, None),)
  681.             if index > context:
  682.                 yield (None, None, None)
  683.                 lines_to_write = context
  684.             else:
  685.                 lines_to_write = index
  686.                 index = 0
  687.             while lines_to_write:
  688.                 i = index % context
  689.                 index += 1
  690.                 yield contextLines[i]
  691.                 lines_to_write -= 1
  692.             lines_to_write = context - 1
  693.             while lines_to_write:
  694.                 (from_line, to_line, found_diff) = line_pair_iterator.next()
  695.                 if found_diff:
  696.                     lines_to_write = context - 1
  697.                 else:
  698.                     lines_to_write -= 1
  699.                 yield (from_line, to_line, found_diff)
  700.  
  701. _file_template = '\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n\n<html>\n\n<head>\n    <meta http-equiv="Content-Type"\n          content="text/html; charset=ISO-8859-1" />\n    <title></title>\n    <style type="text/css">%(styles)s\n    </style>\n</head>\n\n<body>\n    %(table)s%(legend)s\n</body>\n\n</html>'
  702. _styles = '\n        table.diff {font-family:Courier; border:medium;}\n        .diff_header {background-color:#e0e0e0}\n        td.diff_header {text-align:right}\n        .diff_next {background-color:#c0c0c0}\n        .diff_add {background-color:#aaffaa}\n        .diff_chg {background-color:#ffff77}\n        .diff_sub {background-color:#ffaaaa}'
  703. _table_template = '\n    <table class="diff" id="difflib_chg_%(prefix)s_top"\n           cellspacing="0" cellpadding="0" rules="groups" >\n        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>\n        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>\n        %(header_row)s\n        <tbody>\n%(data_rows)s        </tbody>\n    </table>'
  704. _legend = '\n    <table class="diff" summary="Legends">\n        <tr> <th colspan="2"> Legends </th> </tr>\n        <tr> <td> <table border="" summary="Colors">\n                      <tr><th> Colors </th> </tr>\n                      <tr><td class="diff_add"> Added </td></tr>\n                      <tr><td class="diff_chg">Changed</td> </tr>\n                      <tr><td class="diff_sub">Deleted</td> </tr>\n                  </table></td>\n             <td> <table border="" summary="Links">\n                      <tr><th colspan="2"> Links </th> </tr>\n                      <tr><td>(f)irst change</td> </tr>\n                      <tr><td>(n)ext change</td> </tr>\n                      <tr><td>(t)op</td> </tr>\n                  </table></td> </tr>\n    </table>'
  705.  
  706. class HtmlDiff(object):
  707.     _file_template = _file_template
  708.     _styles = _styles
  709.     _table_template = _table_template
  710.     _legend = _legend
  711.     _default_prefix = 0
  712.     
  713.     def __init__(self, tabsize = 8, wrapcolumn = None, linejunk = None, charjunk = IS_CHARACTER_JUNK):
  714.         self._tabsize = tabsize
  715.         self._wrapcolumn = wrapcolumn
  716.         self._linejunk = linejunk
  717.         self._charjunk = charjunk
  718.  
  719.     
  720.     def make_file(self, fromlines, tolines, fromdesc = '', todesc = '', context = False, numlines = 5):
  721.         return self._file_template % dict(styles = self._styles, legend = self._legend, table = self.make_table(fromlines, tolines, fromdesc, todesc, context = context, numlines = numlines))
  722.  
  723.     
  724.     def _tab_newline_replace(self, fromlines, tolines):
  725.         
  726.         def expand_tabs(line):
  727.             line = line.replace(' ', '\x00')
  728.             line = line.expandtabs(self._tabsize)
  729.             line = line.replace(' ', '\t')
  730.             return line.replace('\x00', ' ').rstrip('\n')
  731.  
  732.         fromlines = [ expand_tabs(line) for line in fromlines ]
  733.         tolines = [ expand_tabs(line) for line in tolines ]
  734.         return (fromlines, tolines)
  735.  
  736.     
  737.     def _split_line(self, data_list, line_num, text):
  738.         if not line_num:
  739.             data_list.append((line_num, text))
  740.             return None
  741.         
  742.         size = len(text)
  743.         max = self._wrapcolumn
  744.         if size <= max or size - text.count('\x00') * 3 <= max:
  745.             data_list.append((line_num, text))
  746.             return None
  747.         
  748.         i = 0
  749.         n = 0
  750.         mark = ''
  751.         while n < max and i < size:
  752.             if text[i] == '\x00':
  753.                 i += 1
  754.                 mark = text[i]
  755.                 i += 1
  756.                 continue
  757.             if text[i] == '\x01':
  758.                 i += 1
  759.                 mark = ''
  760.                 continue
  761.             i += 1
  762.             n += 1
  763.         line1 = text[:i]
  764.         line2 = text[i:]
  765.         if mark:
  766.             line1 = line1 + '\x01'
  767.             line2 = '\x00' + mark + line2
  768.         
  769.         data_list.append((line_num, line1))
  770.         self._split_line(data_list, '>', line2)
  771.  
  772.     
  773.     def _line_wrapper(self, diffs):
  774.         for fromdata, todata, flag in diffs:
  775.             if flag is None:
  776.                 yield (fromdata, todata, flag)
  777.                 continue
  778.             
  779.             (fromline, fromtext) = fromdata
  780.             (toline, totext) = todata
  781.             fromlist = []
  782.             tolist = []
  783.             self._split_line(fromlist, fromline, fromtext)
  784.             self._split_line(tolist, toline, totext)
  785.             while fromlist or tolist:
  786.                 if fromlist:
  787.                     fromdata = fromlist.pop(0)
  788.                 else:
  789.                     fromdata = ('', ' ')
  790.                 if tolist:
  791.                     todata = tolist.pop(0)
  792.                 else:
  793.                     todata = ('', ' ')
  794.                 yield (fromdata, todata, flag)
  795.         
  796.  
  797.     
  798.     def _collect_lines(self, diffs):
  799.         fromlist = []
  800.         tolist = []
  801.         flaglist = []
  802.         for fromdata, todata, flag in diffs:
  803.             
  804.             try:
  805.                 fromlist.append(self._format_line(0, flag, *fromdata))
  806.                 tolist.append(self._format_line(1, flag, *todata))
  807.             except TypeError:
  808.                 fromlist.append(None)
  809.                 tolist.append(None)
  810.  
  811.             flaglist.append(flag)
  812.         
  813.         return (fromlist, tolist, flaglist)
  814.  
  815.     
  816.     def _format_line(self, side, flag, linenum, text):
  817.         
  818.         try:
  819.             linenum = '%d' % linenum
  820.             id = ' id="%s%s"' % (self._prefix[side], linenum)
  821.         except TypeError:
  822.             id = ''
  823.  
  824.         text = text.replace('&', '&').replace('>', '>').replace('<', '<')
  825.         text = text.replace(' ', ' ').rstrip()
  826.         return '<td class="diff_header"%s>%s</td><td nowrap="nowrap">%s</td>' % (id, linenum, text)
  827.  
  828.     
  829.     def _make_prefix(self):
  830.         fromprefix = 'from%d_' % HtmlDiff._default_prefix
  831.         toprefix = 'to%d_' % HtmlDiff._default_prefix
  832.         HtmlDiff._default_prefix += 1
  833.         self._prefix = [
  834.             fromprefix,
  835.             toprefix]
  836.  
  837.     
  838.     def _convert_flags(self, fromlist, tolist, flaglist, context, numlines):
  839.         toprefix = self._prefix[1]
  840.         next_id = [
  841.             ''] * len(flaglist)
  842.         next_href = [
  843.             ''] * len(flaglist)
  844.         num_chg = 0
  845.         in_change = False
  846.         last = 0
  847.         for i, flag in enumerate(flaglist):
  848.             if flag:
  849.                 if not in_change:
  850.                     in_change = True
  851.                     last = i
  852.                     i = max([
  853.                         0,
  854.                         i - numlines])
  855.                     next_id[i] = ' id="difflib_chg_%s_%d"' % (toprefix, num_chg)
  856.                     num_chg += 1
  857.                     next_href[last] = '<a href="#difflib_chg_%s_%d">n</a>' % (toprefix, num_chg)
  858.                 
  859.             in_change
  860.             in_change = False
  861.         
  862.         if not flaglist:
  863.             flaglist = [
  864.                 False]
  865.             next_id = [
  866.                 '']
  867.             next_href = [
  868.                 '']
  869.             last = 0
  870.             if context:
  871.                 fromlist = [
  872.                     '<td></td><td> No Differences Found </td>']
  873.                 tolist = fromlist
  874.             else:
  875.                 fromlist = tolist = [
  876.                     '<td></td><td> Empty File </td>']
  877.         
  878.         if not flaglist[0]:
  879.             next_href[0] = '<a href="#difflib_chg_%s_0">f</a>' % toprefix
  880.         
  881.         next_href[last] = '<a href="#difflib_chg_%s_top">t</a>' % toprefix
  882.         return (fromlist, tolist, flaglist, next_href, next_id)
  883.  
  884.     
  885.     def make_table(self, fromlines, tolines, fromdesc = '', todesc = '', context = False, numlines = 5):
  886.         self._make_prefix()
  887.         (fromlines, tolines) = self._tab_newline_replace(fromlines, tolines)
  888.         if context:
  889.             context_lines = numlines
  890.         else:
  891.             context_lines = None
  892.         diffs = _mdiff(fromlines, tolines, context_lines, linejunk = self._linejunk, charjunk = self._charjunk)
  893.         if self._wrapcolumn:
  894.             diffs = self._line_wrapper(diffs)
  895.         
  896.         (fromlist, tolist, flaglist) = self._collect_lines(diffs)
  897.         (fromlist, tolist, flaglist, next_href, next_id) = self._convert_flags(fromlist, tolist, flaglist, context, numlines)
  898.         s = []
  899.         fmt = '            <tr><td class="diff_next"%s>%s</td>%s' + '<td class="diff_next">%s</td>%s</tr>\n'
  900.         for i in range(len(flaglist)):
  901.             if flaglist[i] is None:
  902.                 if i > 0:
  903.                     s.append('        </tbody>        \n        <tbody>\n')
  904.                 
  905.             i > 0
  906.             s.append(fmt % (next_id[i], next_href[i], fromlist[i], next_href[i], tolist[i]))
  907.         
  908.         if fromdesc or todesc:
  909.             header_row = '<thead><tr>%s%s%s%s</tr></thead>' % ('<th class="diff_next"><br /></th>', '<th colspan="2" class="diff_header">%s</th>' % fromdesc, '<th class="diff_next"><br /></th>', '<th colspan="2" class="diff_header">%s</th>' % todesc)
  910.         else:
  911.             header_row = ''
  912.         table = self._table_template % dict(data_rows = ''.join(s), header_row = header_row, prefix = self._prefix[1])
  913.         return table.replace('\x00+', '<span class="diff_add">').replace('\x00-', '<span class="diff_sub">').replace('\x00^', '<span class="diff_chg">').replace('\x01', '</span>').replace('\t', ' ')
  914.  
  915.  
  916. del re
  917.  
  918. def restore(delta, which):
  919.     
  920.     try:
  921.         tag = {
  922.             1: '- ',
  923.             2: '+ ' }[int(which)]
  924.     except KeyError:
  925.         raise ValueError, 'unknown delta choice (must be 1 or 2): %r' % which
  926.  
  927.     prefixes = ('  ', tag)
  928.     for line in delta:
  929.         if line[:2] in prefixes:
  930.             yield line[2:]
  931.             continue
  932.     
  933.  
  934.  
  935. def _test():
  936.     import doctest as doctest
  937.     import difflib as difflib
  938.     return doctest.testmod(difflib)
  939.  
  940. if __name__ == '__main__':
  941.     _test()
  942.  
  943.